射频识别(RFID)技术

NFC 基础知识(翻译)

作者:陈广 日期:2019-11-17


本文描述了在 Android 上执行的基本的 NFC 任务。解释了如何以 NDEF 消息的形式发送和接收 NFC 数据,并描述了支持这些功能的 Android API。更为高级的话题,包括如何使用非 NDEF 数据,请参考《高级 NFC》这篇文章。

在 Android 中使用 NDEF 数据的两个主要情形为:

  • 从 NFC 标签中读取 NDEF 数据
  • 通过 Android Beam 将 NDEF 消息从一个设备发送到另一个设备

从 NFC 标签读取 NDEF 数据的过程由标签调度系统处理。它分析被发现的 NFC 标签,适当地对数据进行分类,并启动对分类数据感兴趣的应用程序。想要处理扫描到的 NFC 标签的应用程序可以通过声明 intent filter 来请求处理该数据。

Android Beam 功能允许设备通过碰一碰,将 NDEF 消息推送到另一个设备上。与其他无线技术(如蓝牙)相比,这种交互提供了一种更简单的发送数据的方法,因为使用 NFC,不需要手动设备发现或配对。当两个设备进入范围时,连接将自动启动。Android Beam 可通过一组 NFC API 获得,因此任何应用程序都可以在设备之间传输信息。例如,Contacts、浏览器和 YouTube 应用程序通过使用 Android Beam 与其它设备分享联系人、网页和视频。

标签调度系统

Android 设备通常在屏幕处于锁定状态时寻找 NFC 标签,除非在设备的设备项中禁用了 NFC。当 Android 设备发现 NFC 标签时,最佳行为是最合适的 activity 不向用户询问的情况下来处理该 intent。因为设备在非常小的范围内扫描 NFC 标签,所以让用户手动选择某个 activity 很可能会迫使他们将设备从标签移开并断开连接。您应当开发只处理所关注 NFC 标签的 activity,防止 Activity 选择器的弹出。

为帮助您实现这一目标,Android 提供了一种特殊的标签调度系统,分析扫描到的 NFC 标签,解析它们,并尝试定位对扫描数据感兴趣的应用程序。通过以下方式实现:

  1. 解析 NFC 标签并计算出标签中标识数据 payload 的 MIME 类型或 URI。
  2. 将 MIME 类型或 URI 以及 payload 封装为 intent。前两个步骤在“如何将 NFC 签映射为 MIME 类型和 URI”这一节中描述。
  3. 启动基于此 intent 的 activity,此内容在“NFC 标签如何分配给应用程序”这一节中描述。

如何将 NFC 标签映射为 MIME 类型和 URI

在开始编写 NFC 应用程序之前,非常重要的是:理解不同类型的 NFC 标签,标签调度系统如何解析 NFC 标签,标签调度系统在检测到 NDEF 消息时所做的特殊工作。NFC 标签有各种各样的技术,也可以使用多种不同的方式向它们写入数据。Android 支持最好的是 NFC 论坛制定的 NDEF 标准。

NDEF 数据被封装在包含一个或多个记录(NdefRecord)的消息(NdefMessage)内部。每个 NDEF 记录必须遵循所需创建记录类型的规范。Android 还支持不包含 NDEF 数据的其他类型的标签,您可以通过使用android.nfc.tech包中的类来处理这些标签。要学习与此有关的更多技术,请参考《高级 NFC》这篇文章。使用这些其他类型的标签需要编写自己的协议栈来与之进行通信,因此我们建议在可能的情况下使用 NDEF 来简化开发,并最大限度地支持 Android 的设备。

注意:要下载完整的 NDEF 规范,请进入NFC 论坛规范及应用文档,并查看“创建 NDEF 记录的通用类型”以获取如何构造 NDEF记录的示例。

现在,您已经了解了一些 NFC 标签的背景知识,接下来的章节将详细描述 Android 如何处理 NDEF 格式标签。当 Android 设备扫描到一个 NFC 标签内包含 NDEF 格式数据时,它会解析信息并试图指出数据的 MIME 类型或识别 URI。为此,系统读取NdefMessage中的第一个NdefRecord,以确定如何解释整个 NDEF 消息(NDEF 消息可以有多个 NDEF 记录)。在格式良好的 NDEF 消息中,第一个NdefRecord包含以下字段:

  1. 3-bit TNF(Type Name Format) 指示如何解释 variable length type 字段。表1描述了有效值。
  2. Variable length type 描述记录的类型,如果为TNF_WELL_KNOWN,则使用此字段指定记录类型定义(Record Type Definition,RTD)。表2描述了有效的 RTD 值。
  3. Variable length ID 记录的唯一标识。此字段不经常使用,但如果需要唯一标识一个标签,可以为之创建一个 ID。
  4. Variable length payload 您要读取或写入的实际数据 payload。NDEF 消息可以包含多个 NDEF 记录,所以不要假定完整的 payload 在 NDEF 消息的第一个 NDEF 记录中。

标签调度系统使用 TNF 和 type 字段尝试向 NDEF 信息映射 MIME 类型或 URI,如果成功,它将这些信息与实际 payload 封装到ACTION_NDEF_DISCOVERED intent 中。但是,在某些情况下,标记分发系统无法根据第一个 NDEF 记录确定数据类型。当 NDEF 数据无法映射到 MIME 类型或 URI 时,或 NFC 标签不包含要开始的 NDEF 数据时,会发生这种情况。在这种情况下,包含有关标签技术和 payload 的信息的Tag对象被封装在ACTION_TECH_DISCOVERED intent意图中。

表示描述了标签调度系统如何将 TNF 以及 type 字段映射到 MIME 类型或 URI 中。它还描述了哪个 TNF 不能被映射到 MIME 类型或 URI 中。这种情况下,标签调度系统返回到ACTION_TECH_DISCOVERED中。

例如,如果标签调度系统遇到类型为TNF_ABSOLUTE_URI的记录,它将这条记录的 variable length type 字段映射为一个 URI。标签调度系统将此 URI 与标签的其它信息封装进ACTION_NDEF_DISCOVERED intent 的数据字段,如 payload。另一方面,如果遇到一个类型为TNF_UNKNOWN的记录,它仅创建一个封装了标签技术的 intent。

表1:支持的 TNF 以及它们的映射

Type Name Format(TNF) 映射
TNF_ABSOLUTE_URI 基于 type 字段的 URI
TNF_EMPTY 返回到 ACTION_TECH_DISCOVERED
TNF_EXTERNAL_TYPE 基于 type 字段中 URN 的 URI。URN 以缩略的形式编码到 NDEF type 字段中:<domain_name>:<service_name>。Android 将其映射为如下形式 URI:vnd.android.nfc://ext/<domain_name>:<service_name>
TNF_MIME_MEDIA 基于 type 字段的 MIME 类型
TNF_UNCHANGED 在第一条记录中无效,因此返回到 ACTION_TECH_DISCOVERED
TNF_UNKNOWN 返回到 ACTION_TECH_DISCOVERED
TNF_WELL_KNOWN MIME 类型或 URI 取决于您在 type 字段中设置的记录类型定义(RTD)

表2: TNF_WELL_KNOWN 支持的 RTD 及其映射

记录类型定义(RTD) 映射
RTD_ALTERNATIVE_CARRIER 返回到 ACTION_TECH_DISCOVERED
RTD_HANDOVER_CARRIER 返回到 ACTION_TECH_DISCOVERED
RTD_HANDOVER_REQUEST 返回到 ACTION_TECH_DISCOVERED
RTD_HANDOVER_SELECT 返回到 ACTION_TECH_DISCOVERED
RTD_SMART_POSTER 基于解析 payload 的 URI
RTD_TEXT text/plain 类型的 MIME
RTD_URI 基于 payload 的 URI

NFC 标签是如何分发给应用程序的

当标签调度系统完成了创建封装 NFC 标签及其标识信息的 intent 时,它将该 intent 发送到对该 intent 进行过滤的感兴趣的应用。如果一个以上的应用程序能够处理该 intent,则弹出 Activity 选择器,以便用户可以选择 Activity。标签调度系统定义了三个 intent,下面以高到低的优先级顺序列出:

  1. ACTION_NDEF_DISCOVERED:当扫描到包含 NDEF payload 的可识别类型的标签时,此 intent 用于启动 Activity。这是最高优先级的 intent,并且标签调度系统尽可能在任何其他 intent 之前启动具有该 intent 的 Activity。
  2. ACTION_TECH_DISCOVERED:如果没有 activity 注册来处理ACTION_NDEF_DISCOVERED intent,标签调度系统尝试启动一个此 intent 的应用程序。如果扫描的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者标签不包含 NDEF 数据,而是使用已知的标签技术,则也直接启动此 intent(而不首先启动ACTION_NDEF_DISCOVERED)。
  3. ACTION_TAG_DISCOVERED:如果没有 activity 处理ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED intent,则启动此 intent。

标签调度系统工作的基本方式如下:

  1. 在解析 NFC 标签时,尝试为标签调度系统创建的 intent 启动一个 Activity(ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED)。
  2. 如果无针对此 intent 的 activity 过滤器,则尝试启动更低优先级 intent 的 Activity(ACTION_TECH_DISCOVEREDACTION_TAG_DISCOVERED),直到应用程序筛选出 intent,或者直到标签调度系统尝试了所有可能的 intent。
  3. 如果没有应用程序过滤器针对此 intent,则什么也不做。

图 1:标签调度系统

只要有可能,就使用 NDEF 消息和ACTION_NDEF_DISCOVERED intent,因为在这三种消息中,NDEF 是最具体的。此意图相对于其他两个 intent 来说,会在更合适的时间启动您的应用程序,从而给用户更佳的体验。

在 Android manifest 中请求 NFC 访问

在您访问设备的 NFC 硬件并正确处理 NFC intent 之前,请在 AndroidManifest.xml 文件中声明这些项:

  • 用来访问 NFC 硬件的<uses-permission>元素:
<uses-permission android:name="android.permission.NFC" />
  • 应用程序可支持的最小 SDK 版本。API 级别 9 只支持通过ACTION_TAG_DISCOVERED进行有限的标签调度,并且只允许通过额外的EXTRA_NDEF_MESSAGES访问 NDEF 消息。其他标签属性或 I/O 操作都是不可访问的。API 级别 10 包括全面的读取/写入支持以及前台 NDEF 推送,而 API 级别 14 提供了一种更容易将 NDEF 消息推送到其他设备的方法,包括 Android Beam 和创建 NDEF 记录的方便方法。
<uses-sdk android:minSdkVersion="10"/>
  • uses-feature元素,这样,您的应用程序只会出现在拥有 NFC 硬件的设备上:
<uses-feature android:name="android.hardware.nfc" android:required="true" />

如果您的应用程序使用 NFC 功能,但该功能对应用程序来说并不重要,那么可以在运行时通过检查getDefaultAdapter()是否为null来省略uses-feature元素,并检查 NFC 的可用性。

NFC intent 过滤器

当您希望处理的 NFC 标签被扫描时,会启动应用程序,您的应用程序可以在 Android manfiest 中筛选一个、两个、或所有三个 NFC intent。但是在应用程序启动时,通常筛选最想控制的ACTION_NDEF_DISCOVERED intent。当应用程序并未筛选ACTION_NDEF_DISCOVERED或当 payload 并非 NDEF 时,才回退至ACTION_TECH_DISCOVERED intent。ACTION_TAG_DISCOVERED通常类型过于宽泛,无法筛选。大多数应用程序会在ACTION_TAG_DISCOVERED之前筛选ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED,因此,您的应用程序启动的概率很低。ACTION_TAG_DISCOVERED通常仅在没有其它应用程序处理ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED intent 时的最后手段。

因为 NFC 标签的部署不同,并且大多数时候并不在您的控制下,这就是在必要时可以回退到其他两个 intent 的原因。当您对标签和写入数据的类型进行控制时,建议使用 NDEF 格式化标签。以下各节介绍如何筛选每种类型的 intent。

ACTION_NDEF_DISCOVERED

要筛选ACTION_NDEF_DISCOVERED intent,请声明 intent filter 以及您想筛选的数据的类型。下例演示了text/plain MIME 类型的ACTION_NDEF_DISCOVERED intent:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

下面的示例是 http://developer.android.com/index.html 形式的 URI 的筛选器。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
   <data android:scheme="http"
              android:host="developer.android.com"
              android:pathPrefix="/index.html" />
</intent-filter>

ACTION_TECH_DISCOVERED

如果您的 activity 筛选器用于ACTION_TECH_DISCOVERED intent,必须创建一个 XML 资源文件并在tech-list集合中指定您的 activity 支持的技术。如果tech-list集合是所支持的标签技术(可通过调用getTechList()获取)的子集,则 activity 被认为是匹配的。

例如,如果扫描的标签支持 MifareClassic、NdefFormatable 和 NfcA,为匹配 activity,tech-list集合必须指定所有三个、两个或其中一个技术。

以下示例定义了所有技术。您可以移除其中不需要的。将其保存在<project-root>/res/xml文件夹中(名字任意)。

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.NfcF</tech>
        <tech>android.nfc.tech.NfcV</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

您还可以指定多个tech-list集合。每个tech-list集合都被独立地考虑,如果任何单个tech-list集合是GetTechList()返回的技术的子集,则您的 activity 被视为匹配。这为所匹配技术提供了“与”和“或”语义。以下示例所匹配的标签支持 NfcA 和 Ndef 技术,或者支持 NfcB 和 Ndef 技术:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>

在 AndroidManifest.xml 文件中,指定您刚才在<activity>元素的<meta-data>元素中创建的资源文件,如下:

<activity>
...
<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>

<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
    android:resource="@xml/nfc_tech_filter" />
...
</activity>

获取更多有关如何使用标签技术和ACTION_TECH_DISCOVERED intent 的信息,请参考《高级 NFC》这篇文档中的“使用支持的标签技术”这一节。

ACTION_TAG_DISCOVERED

为筛选ACTION_TAG_DISCOVERED,请使用以下 intent filter:

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>

从 intent 中获取信息

如果一个 activity 是因为 NFC intent 启动的,您可以从 intent 中获取被扫描 NFC 标签的信息。intent 可以包含以下附加内容,具体取决于扫描到的标签:

  • EXTRA_TAG(必须):一个表现被扫描标签的Tag对象。
  • EXTRA_NDEF_MESSAGES(可选):坐标签解析的 NDEF 消息数组。此额外信息在ACTION_NDEF_DISCOVERED intent 中是必须的。
  • EXTRA_ID(可选):标签的低级别 ID。

为获取这些信息,检查您的 activity 是由某个 NFC intent 启动,以确保标签被扫描到,然后获取 intent 中获取额外信息。以下示例检查ACTION_NDEF_DISCOVERED intent 并获取 NDEF 信息。

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    ...
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
        Parcelable[] rawMessages =
            intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (rawMessages != null) {
            NdefMessage[] messages = new NdefMessage[rawMessages.length];
            for (int i = 0; i < rawMessages.length; i++) {
                messages[i] = (NdefMessage) rawMessages[i];
            }
            // Process the messages array.
            ...
        }
    }
}

或者,您可以从 intent 获取Tag对象,它将包含 payload 并允许您枚举标签的技术:

Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

创建 NDEF 记录的通用类型

本节描述如何创建 NDEF 记录的公共类型,以帮助您使用 Android Beam 时向 NFC 标签写入或发送数据。从 Android 4.0(API 等级 14)开始,createUri方法可用于帮助您自动创建 URI 记录。从 Android 4.1(API 等级 16)开始,createExternal()createMime()可用于帮助您创建 MIME 和 外部类型的 NDEF 记录。无论何时,在手动创建 NDEF 记录时,请使用这些助手方法以避免错误。

本节还描述如何为记录创建相应的 intent filter。所有这些 NDEF 记录示例都应当是您向标签或 Beam 中写入的 NDEF 消息中的第一条 NDEF 记录。

TNF_ABSOLUTE_URI

注意:我们更推荐使用RTD_URI类型而不是TNF_ABSOLUTE_URI,因为它更有效率。

您可以通过以下方式创建一个TNF_ABSOLUTE_URI NDEF 记录:

NdefRecord uriRecord = new NdefRecord(
    NdefRecord.TNF_ABSOLUTE_URI ,
    "http://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")),
    new byte[0], new byte[0]);

NDEF 记录的 intent filter 应当如下所示:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="http"
        android:host="developer.android.com"
        android:pathPrefix="/index.html" />
</intent-filter>

TNF_MIME_MEDIA

您可以通过以下方式创建一个TNF_MIME_MEDIA NDEF 记录:

使用createMime()方法:

NdefRecord mimeRecord = NdefRecord.createMime("application/vnd.com.example.android.beam",
    "Beam me up, Android".getBytes(Charset.forName("US-ASCII")));

手动创建NdefRecord

NdefRecord mimeRecord = new NdefRecord(
    NdefRecord.TNF_MIME_MEDIA ,
    "application/vnd.com.example.android.beam".getBytes(Charset.forName("US-ASCII")),
    new byte[0], "Beam me up, Android!".getBytes(Charset.forName("US-ASCII")));

NDEF 记录的 intent filter 应当如下所示:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="application/vnd.com.example.android.beam" />
</intent-filter>

RTD_TEXT 的 TNF_WELL_KNOWN

您可以通过以下方式创建一个TNF_WELL_KNOWN NDEF 记录:

public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8) {
    byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
    Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
    byte[] textBytes = payload.getBytes(utfEncoding);
    int utfBit = encodeInUtf8 ? 0 : (1 << 7);
    char status = (char) (utfBit + langBytes.length);
    byte[] data = new byte[1 + langBytes.length + textBytes.length];
    data[0] = (byte) status;
    System.arraycopy(langBytes, 0, data, 1, langBytes.length);
    System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
    NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
    NdefRecord.RTD_TEXT, new byte[0], data);
    return record;
}

NDEF 记录的 intent filter 应当如下所示:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
</intent-filter>

RTD_URI 的 TNF_WELL_KNOWN

您可以通过以下方式创建一个TNF_WELL_KNOWN NDEF 记录:

使用createUri(String)方法:

NdefRecord rtdUriRecord1 = NdefRecord.createUri("http://example.com");

使用createUri(Uri)方法:

Uri uri = Uri.parse("http://example.com");
NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);

手动创建NdefRecord

byte[] uriField = "example.com".getBytes(Charset.forName("US-ASCII"));
byte[] payload = new byte[uriField.length + 1];              //add 1 for the URI Prefix
payload[0] = 0x01;                                           //prefixes http://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.length);  //appends URI to payload
NdefRecord rtdUriRecord = new NdefRecord(
    NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], payload);

NDEF 记录的 intent filter 应当如下所示:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="http"
        android:host="example.com"
        android:pathPrefix="" />
</intent-filter>

TNF_EXTERNAL_TYPE

您可以通过以下方式创建一个TNF_EXTERNAL_TYPE NDEF 记录:

使用createExternal()方法:

byte[] payload; //assign to your data
String domain = "com.example"; //usually your app's package name
String type = "externalType";
NdefRecord extRecord = NdefRecord.createExternal(domain, type, payload);

手动创建NdefRecord

byte[] payload;
...
NdefRecord extRecord = new NdefRecord(
    NdefRecord.TNF_EXTERNAL_TYPE, "com.example:externalType".getBytes(Charset.forName("US-ASCII")),
    new byte[0], payload);

NDEF 记录的 intent filter 应当如下所示:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="vnd.android.nfc"
        android:host="ext"
        android:pathPrefix="/com.example:externalType"/>
</intent-filter>

使用TNF_EXTERNAL_TYPE进行更通用的 NFC 标签部署,以便更好地支持 Android 驱动和非 Android 驱动的设备。

注意:TNF_EXTERNAL_TYPE 的 URN 的规范格式为:

urn:nfc:ext:example.com:externalType, 但是 NFC 论坛 RTD 规范声明:URN 的urn:nfc:ext:部分必须在 NDEF 中省略。所以您所做的只是提供域(示例中的example.com)和类型(示例中的externalType),并用冒号分隔。在分发TNF_EXTERNAL_TYPE时,Android 将 URN urn:nfc:ext:example.com:externalType转换为 URI vnd.android.nfc://ext/example.com:externalType,这就是本示例中的 intent filter 声明的内容。

Android 应用程序记录

在 Android 4.0(API 等级 14)中引入了 Android Application Record (AAR) ,它在扫描到 NFC 标签并启动应用程序时,提供了更强的确定性。AAR 具有嵌入 NDEF 记录中的应用程序的包名。您可以在 NDEF 消息的任意 NDEF 记录中添加 AAR,因为 Android 会在整个 NDEF 消息中搜索 AAR。如果找到 AAR,就基于 AAR 内的包名称启动应用程序。.如果应用程序不在设备上,则启动 Google Play 下载应用程序。

如果您希望阻止其他应用程序过滤相同的 intent 并可能处理您部署的特定标签,则 AAR 是非常有用的。由于包名约束,AAR 只在应用程序级别上被支持,而不是像 intent filter 那样在 Activity 级别上被支持。如果希望在 Activity 级别上处理 intent,请使用 intent filter。

如果标签中包含 AAR,标签调度系统将使用如下方式分发:

  1. 尝试正常使用 intent filter 启动 Activity。如果与 intent 匹配的 Activity 也与 AAR 匹配,则启动该 Activity。
  2. 如果 Activity 的 intent filter 与 AAR 不匹配,如果多个 Activity 可以处理该 intent,或者如果没有 Activity 处理该意图,则启动 AAR 指定的应用程序。
  3. 如果没有任何应用程序可以从 AAR 启动,转到 Google Play 下载基于 AAR 的应用程序。

注意:您可以用前台调度系统覆盖 AAR 和 intent 调度系统,这使得在发现 NFC 标签时,前台 activity 具有优先级。使用这种方法,activity 必须在前台覆盖 AAR 和 intent 调度系统。

如果仍然希望对不包含 AAR 的被扫描标签进行筛选,则可以将 intent filter 声明为正常。如果应用程序对不包含 AAR 的其他标签感兴趣,这将非常有用。例如,您可能希望保证您的应用程序处理您部署的专有标签以及第三方部署的通用标签。请记住,AAR 仅用于 Android 4.0 设备或更高版本,因此当部署标签时,最可能希望使用 AAR 和 MIME类型/URI的组合来支持最大范围的设备。此外,在部署 NFC 标签时,请考虑如何编写 NFC 标签以支持大多数设备(Android 驱动的设备和其他设备)。您可以通过定义一个相对独特的 MIME 类型或 URI 来实现这一点,以使应用程序更容易区分。

Android 提供了一个简单的 API createApplicationRecord()用于创建 AAR。您只需将 AAR 嵌入到NdefMessage中的任何位置。您不希望使用NdefMessage的第一个记录,除非 AAR 是NdefMessage中的唯一记录。这是因为 Android 系统检查NdefMessage的第一记录以确定标签的 MIME 类型或 URI,该 MIME 类型或 URI 用于创建应用中用于过滤的 intent。下列代码演示了 AAR 的创建:

NdefMessage msg = new NdefMessage(
        new NdefRecord[] {
            ...,
            NdefRecord.createApplicationRecord("com.example.android.beam")}
        );
)

向其它设备 Beam NDEF 消息

Android Beam 允许两个 Android 设备之间进行简单的对等数据交换。要将数据传送到另一个设备的应用程序必须在前台,并且接收数据的设备不能被锁定。当 Beam 装置与接收装置紧密接触时,Beam 装置显示“Touch to Beam” UI。用户可选择是否向接收设备 Beam 消息。

注意:API 等级 10 允许前台 NDEF 推送,它提供了与 Android Beam 类似的功能。这些 API 已被废弃,但可用于支持较老的设备。参考enableForegroundNdefPush()获取更多信息。

您可以通过调用以下两个方法之一来启用 Android Beam:

  • setNdefPushMessage():接收一个NdefMessage设置用于 Beam 的消息。当两个设备足够接近时自动 Beam 消息。
  • setNdefPushMessageCallback():接收一个包含createNdefMessage()的回调,当一个设备在 Beam 数据的范围内时,调用它。回调只允许在必要时创建 NDEF 消息。

一个 activity 一次仅能推送一条 NDEF 消息,因此,如果两者都设置了,则setNdefPushMessageCallback()优先于setNdefPushMessage()。要使用 Android Beam,必须遵守以下通用准则:

  • Beam 数据的 activity 必须在前台。这两个设备必须解锁它们的屏幕。
  • 您必须将正在发送的数据封装在NdefMessage对象中。
  • 接收 Beam 数据的 NFC 设备必须支持com.android.npp NDEF 推送协议或 NFC 论坛 SNEP(Simple NDEF Exchange Protocol,简单 NDEF 交换协议)。com.android.npp协议需要设备在 API 等级 9 (Android 2.3) 至 API 等级 13 (Android 3.2)上。com.android.npp和 SNEP 都需要在 API 等级 14 (Android 4.0) 以之后版本。

注意:如果您的 activity 启用 Android Beam 并处于前台,则禁用标准 intent 调度系统。但是,如果您的 activity 还启用了前台分配,那么它仍然可以扫描与前台分配中设置的 intent filter 相匹配的标签。

要启用 Android Beam:

  1. 创建一个包含要推送到另一个设备上的NdefRecordNdefMessage
  2. 在 activity 的onCreate()方法中调用携带NdefMessagesetNdefPushMessage(),或者在NfcAdapter.CreateNdefMessageCallback对象中调用setNdefPushMessageCallback。这些方法需要至少一个要启用 Android Beam 的 activity 以及其他要激活的 activity 的可选列表。

通常,如果在两个设备处于通信范围内的任何时候,您的 activity 如果只需要推送相同的 NDEF 消息,则使用setNdefPushMessage()。当您的应用程序关心应用程序的当前 context 并希望根据用户在应用程序中的操作推送 NDEF 消息时,可以使用setNdefPushMessageCallback

以下示例演示了一个简单的 activity 是如何在onCreate方法中调用NfcAdapter.CreateNdefMessageCallback的(参考AndroidBeamDemo获取完整示例)。此示例还拥有帮您创建 MIME 记录的方法:

package com.example.android.beam;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;
import java.nio.charset.Charset;


public class Beam extends Activity implements CreateNdefMessageCallback {
    NfcAdapter nfcAdapter;
    TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView) findViewById(R.id.textView);
        // Check for available NFC Adapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        // Register callback
        nfcAdapter.setNdefPushMessageCallback(this, this);
    }

    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        String text = ("Beam me up, Android!\n\n" +
                "Beam Time: " + System.currentTimeMillis());
        NdefMessage msg = new NdefMessage(
                new NdefRecord[] { createMime(
                        "application/vnd.com.example.android.beam", text.getBytes())
         /**
          * The Android Application Record (AAR) is commented out. When a device
          * receives a push with an AAR in it, the application specified in the AAR
          * is guaranteed to run. The AAR overrides the tag dispatch system.
          * You can add it back in to guarantee that this
          * activity starts when receiving a beamed message. For now, this code
          * uses the tag dispatch system.
          */
          //,NdefRecord.createApplicationRecord("com.example.android.beam")
        });
        return msg;
    }

    @Override
    public void onResume() {
        super.onResume();
        // Check to see that the Activity started due to an Android Beam
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            processIntent(getIntent());
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        // onResume gets called after this to handle the intent
        setIntent(intent);
    }

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    void processIntent(Intent intent) {
        textView = (TextView) findViewById(R.id.textView);
        Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
                NfcAdapter.EXTRA_NDEF_MESSAGES);
        // only one message sent during the beam
        NdefMessage msg = (NdefMessage) rawMsgs[0];
        // record 0 contains the MIME type, record 1 is the AAR, if present
        textView.setText(new String(msg.getRecords()[0].getPayload()));
    }
}

注意,此代码注释掉了一个 ARR,它是可移除的。如果启用 AAR,则 AAR 中指定的应用程序总是接收 Android Beam 消息。如果应用程序不存在,则启动 Google Play 以下载应用程序。因此,如果使用 AAR,则以下 intent filter 对于 Android 4.0 设备或更高版本在技术上来说并不是必需的:

<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <data android:mimeType="application/vnd.com.example.android.beam"/>
</intent-filter>

有了这个 intent filter,com.example.android.beam应用程序在碰到以下情况时就可以启动了:当它扫描 NFC 标签或接收带有com.example.android.beam类型 AAR 的 Android Beam 时,或者当 NDEF 格式消息包含application/vnd.com.example.android.beam类型的 MIME 记录时。

尽管 AAR 保证启动或下载应用程序,但还是建议使用 intent filter,因为它们允许您在应用程序中启动您选择的 activity,而不是总是在 AAR 指定的包中启动主 Activity。AAR 没有 Activity 级别的粒度。另外,由于一些 Android 驱动的设备不支持 AAR,您还应该在 NDEF 消息的第一个 NDEF 记录中嵌入标识信息,并为此进行筛选,以防万一。有关如何创建记录的更多信息,请参见“创建 NDEF记录的通用类型”。

;

© 2018 - IOT小分队文章发布系统 v0.3